package com.example.financeapi.config.aspect;

import com.example.financeapi.config.annotation.SystemJobLog;
import com.example.financeapi.entity.FTaskRecordLog;
import com.example.financeapi.service.FTaskRecordLogService;
import com.example.financeapi.utils.HttpUtil;
import com.google.gson.Gson;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/***
 * 切面-获取Job中的定时任务操作记录
 * @author Alvin
 */
@Aspect
@Component
@Order(1)
public class JobLogAspect {

    private static final Logger log = LoggerFactory.getLogger(JobLogAspect.class);

    @Autowired
    private FTaskRecordLogService fTaskRecordLogService;

    /**
     * @Pointcut配置要监控的方法，本例中文监控所有控制器的方法
     * 通过@annotation来配置切点，代表我们的AOP切面会切到所有用EagleEye注解修饰的类
     *  之前写法：@Pointcut("execution(public * com.example.financeapi.job.*.*(..))")
     */
    @Pointcut("@annotation(com.example.financeapi.config.annotation.SystemJobLog)")
    public void logPointCut() {
    }

    /**
     * 前置通知  用于拦截Controller层记录用户的操作
     * @param joinPoint
     * @throws Throwable
     */
    @Before("logPointCut()") //注解@Before会在进入切片监控方法方法之前执行
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        // 接收到请求，记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if(attributes!=null){
            HttpServletRequest request = attributes.getRequest();
            /**获得本机IP*/
            String addr = InetAddress.getLocalHost().getHostAddress();
            // 记录下请求内容
            log.info("请求地址 URL : " + request.getRequestURL().toString());
            log.info("请求token ：" + request.getHeader("token"));
            log.info("HTTP_METHOD : " + request.getMethod());
            log.info("IP : " + request.getRemoteAddr());
            log.info("方法描述 Description : " + getMethodDescription(joinPoint));
            log.info("IP : " + addr);
            log.info("请求方法 CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
            log.info("参数 ARGS : " + Arrays.toString(joinPoint.getArgs()));
        }
    }

    /**
     * @AfterReturning会在切片监控方法即将返回时执行
     * returning的值和doAfterReturning的参数名一致
     * @param ret
     * @throws Throwable
     */
    @AfterReturning(returning = "ret", pointcut = "logPointCut()")
    public void doAfterReturning(Object ret) throws Throwable {
        // 处理完请求，返回内容
        //log.info("返回值 : " + ret);
    }

    /**
     * 环绕通知 @Around
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("logPointCut()")
    public Object doAround(ProceedingJoinPoint point) throws Throwable {
        long startTime = System.currentTimeMillis();
        /*obj 为方法的返回值*/
        Object obj = point.proceed();
        long times = System.currentTimeMillis() - startTime;
        if(!"run".equals(point.getSignature().getName())){
            saveJobLog(point,startTime,(System.currentTimeMillis()), times);
            log.info("调用方法：{}，方法描述：{}，IP：{}，执行耗时：{}豪秒",point.getSignature().toString(),getMethodDescription(point),HttpUtil.getLocalIPList(),times);
        }
        return obj;
    }


    /**
     * 获取注解中对方法的描述信息
     * @param joinPoint
     * @return
     * @throws Exception
     */
    public static String getMethodDescription(JoinPoint joinPoint)throws Exception {
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        String description = "";
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    description = method.getAnnotation(SystemJobLog.class).description();
                    break;
                }
            }
        }
        return description;
    }

    /**
     * 保存日志
     * @param joinPoint
     * @param startTime
     * @param endTime
     * @param times
     * @throws Exception
     */
    private void saveJobLog(ProceedingJoinPoint joinPoint, long startTime, long endTime, long times) throws Exception {
        SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        String startTimeStr = dateformat.format(startTime);
        String endTimeStr = dateformat.format(endTime);
        FTaskRecordLog sysLog = new FTaskRecordLog();
        //请求的 类名、方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        String classMethod =joinPoint.getSignature().getDeclaringTypeName() + "." + methodName;
        sysLog.setClassName(className);
        sysLog.setMethodName(methodName);
        sysLog.setClassMethod(classMethod);
        sysLog.setMethodDescription(getMethodDescription(joinPoint));
        //请求的参数
        Object[] args = joinPoint.getArgs();
        List<String> list = new ArrayList<String>();
        for (Object o : args) {
            list.add(new Gson().toJson(o));
        }
        sysLog.setParams(list.toString());
        sysLog.setExeutime(times+"");
        sysLog.setIp(HttpUtil.getLocalIPList().toString());
        sysLog.setStartTime(startTimeStr);
        sysLog.setEndTime(endTimeStr);
        fTaskRecordLogService.insert(sysLog);
    }

}